// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package codehost

import (
	"archive/zip"
	"encoding/xml"
	"fmt"
	"io"
	"os"
	"path"
	"path/filepath"
	"time"
)

func svnParseStat(rev, out string) (*RevInfo, error) {
	var log struct {
		Logentry struct {
			Revision int64  `xml:"revision,attr"`
			Date     string `xml:"date"`
		} `xml:"logentry"`
	}
	if err := xml.Unmarshal([]byte(out), &log); err != nil {
		return nil, vcsErrorf("unexpected response from svn log --xml: %v\n%s", err, out)
	}

	t, err := time.Parse(time.RFC3339, log.Logentry.Date)
	if err != nil {
		return nil, vcsErrorf("unexpected response from svn log --xml: %v\n%s", err, out)
	}

	info := &RevInfo{
		Name:    fmt.Sprintf("%d", log.Logentry.Revision),
		Short:   fmt.Sprintf("%012d", log.Logentry.Revision),
		Time:    t.UTC(),
		Version: rev,
	}
	return info, nil
}

func svnReadZip(dst io.Writer, workDir, rev, subdir, remote string) (err error) {
	// subversion CLI不提供写入存储库的命令
	// 直接到归档文件，因此我们需要将其导出到本地文件系统
	// 相反不幸的是，本地文件系统可能会应用任意
	// 规范化文件名，因此我们需要直接获取这些文件名。
	// None
	// “svn导出”在写入文件名时打印文件名，但在读取
	// svn源代码（自1868933修订版起），这些文件名使用
	// 系统区域设置，而不是从原点逐字节保留。对于
	// 我们的目的，那是不行的，但我们不想到处乱搞
	// 用户的区域设置-可能会影响错误消息，以及
	// 我们不知道用户有哪些可用的区域设置或LC_*变量
	// 他们的平台支撑。
	// None
	// 相反，我们将执行两次导出：首先，我们将运行“svn list”以获取
	// 规范文件名，然后我们将“svn导出”并查找这些文件名
	// 在本地文件系统中。（如果此时存在编码问题，我们将
	// 可能会拒绝生成的模块。）

	remotePath := remote
	if subdir != "" {
		remotePath += "/" + subdir
	}

	out, err := Run(workDir, []string{
		"svn", "list",
		"--non-interactive",
		"--xml",
		"--incremental",
		"--recursive",
		"--revision", rev,
		"--", remotePath,
	})
	if err != nil {
		return err
	}

	type listEntry struct {
		Kind string `xml:"kind,attr"`
		Name string `xml:"name"`
		Size int64  `xml:"size"`
	}
	var list struct {
		Entries []listEntry `xml:"entry"`
	}
	if err := xml.Unmarshal(out, &list); err != nil {
		return vcsErrorf("unexpected response from svn list --xml: %v\n%s", err, out)
	}

	exportDir := filepath.Join(workDir, "export")
	// 从以前的（失败的）运行中删除任何现有内容。
	if err := os.RemoveAll(exportDir); err != nil {
		return err
	}
	defer os.RemoveAll(exportDir) // 尽力

	_, err = Run(workDir, []string{
		"svn", "export",
		"--non-interactive",
		"--quiet",

		// 抑制任何依赖于平台或主机的转换。
		"--native-eol", "LF",
		"--ignore-externals",
		"--ignore-keywords",

		"--revision", rev,
		"--", remotePath,
		exportDir,
	})
	if err != nil {
		return err
	}

	// 将导出的文件从文件系统中刮出，并在zipfile中对其进行编码。

	// “zip文件中的所有文件都应
	// 嵌套在单个顶级目录中，未指定其名称。“
	// 我们将（任意）选择远程路径的基。
	basePath := path.Join(path.Base(remote), subdir)

	zw := zip.NewWriter(dst)
	for _, e := range list.Entries {
		if e.Kind != "file" {
			continue
		}

		zf, err := zw.Create(path.Join(basePath, e.Name))
		if err != nil {
			return err
		}

		f, err := os.Open(filepath.Join(exportDir, e.Name))
		if err != nil {
			if os.IsNotExist(err) {
				return vcsErrorf("file reported by 'svn list', but not written by 'svn export': %s", e.Name)
			}
			return fmt.Errorf("error opening file created by 'svn export': %v", err)
		}

		n, err := io.Copy(zf, f)
		f.Close()
		if err != nil {
			return err
		}
		if n != e.Size {
			return vcsErrorf("file size differs between 'svn list' and 'svn export': file %s listed as %v bytes, but exported as %v bytes", e.Name, e.Size, n)
		}
	}

	return zw.Close()
}
